엘릭서에서는 기본적으로 프로세스가 짧은 생명 주기를 가집니다. 함수가 완료되면 프로세스는 종료됩니다. 지속적인 상태를 가진 프로세스를 만들기 위해 지속적이고 상태를 유지하는 프로세스재귀를 사용하여 프로세스를 루프 안에서 계속 살아 있게 합니다.
1. 꼬리 호출 최적화 (TCO)
함수의 절대 마지막 동작이 자기 자신을 호출하는 경우, 에르랭 가상머신(빔)은 꼬리 호출 최적화를 수행합니다. 스택에 새로운 프레임을 추가하는 대신 단순히 함수 시작 부분으로 다시 점프 새로운 인수와 함께 함수의 시작 부분으로 이동합니다.
def factorial(n, acc), do: _fact(n-1, acc*n) # TCO
def factorial(n), do: n * factorial(n-1) # TCO 아님
def factorial(n), do: n * factorial(n-1) # TCO 아님
2. 지속적 상태
상태는 재귀 호출에 업데이트된 값을 인수로 전달함으로써 유지됩니다. TCO 덕분에 이 인수들은 스택 위의 원래 매개변수를 대체하며 추가 메모리를 소비하지 않아, 루프가 무한히 실행될 수 있습니다.
main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
QUESTION 1
What is the primary requirement for Tail-Call Optimization to occur?
The function must use the 'loop' keyword.
The recursive call must be the absolute final expression executed.
The function must be defined inside a module.
Arguments must be integers.
✅ Correct!
Correct. If any operation occurs after the call (like multiplication), the frame remains on the stack, leading to exhaustion.❌ Incorrect
TCO requires that no work remains after the recursive call so the VM can safely jump back to the start.QUESTION 2
Exercise: WorkingWithMultipleProcesses-1. Run the Spawn1 and Spawn4 code. See if you get comparable results. What is the observable difference?
Spawn1 crashes after one message; Spawn4 stays alive.
Spawn1 is faster than Spawn4.
Spawn4 uses more memory over time.
There is no observable difference.
✅ Correct!
Reference Answer: When running spawn1.exs, the process handles one message and dies. In spawn4.exs, the process recurses, remaining in the process list and capable of responding to subsequent messages.❌ Incorrect
Check the process lifecycle. One-off processes terminate immediately after their function block finishes.QUESTION 3
In pmap, we assign 'self' to 'me' before spawning. Why?
To make the code more readable.
Because 'self' inside a spawn block refers to the child process PID.
To bypass the Pin operator requirements.
✅ Correct!
Correct! If you used 'self' inside the child's block, the child would try to send the message to itself, not the parent.❌ Incorrect
Remember that 'self()' is a dynamic call; its value depends on which process is currently executing the code.QUESTION 4
Use spawn_link to start a child that sends a message and exits. If the parent sleeps for 500ms before receiving, what happens to the message?
The message is lost because the parent wasn't waiting.
The message is stored in the parent's mailbox until processed.
The parent crashes immediately.
✅ Correct!
Reference Answer: The message is placed in the mailbox. It does not matter that you weren't waiting; the BEAM buffers incoming messages. If trapping exits, you would receive both the custom message and the {:EXIT, pid, :normal} signal.❌ Incorrect
Elixir processes have mailboxes that act as buffers for incoming data.QUESTION 5
Is the order of replies in concurrent processes deterministic in theory?
Yes, they always arrive in spawn order.
No, it depends on the scheduler and execution time.
✅ Correct!
Correct. In practice, we use the Pin operator (^pid) to ensure we receive messages in a specific, deterministic order regardless of completion speed.❌ Incorrect
Concurrent execution is inherently non-deterministic. We must enforce order in our code logic.Case Study: Token Exchange Persistence
WorkingWithMultipleProcesses-2
You need to spawn two processes that receive unique tokens ('fred' and 'betty') and send them back. You must ensure the results are gathered in the correct order even if the processes finish at different times.
Q
How can you make the order of received tokens deterministic in practice?
Solution:
By capturing the PIDs of the spawned processes and using the Pin operator (^pid) in the receive block. This forces the parent to wait for the message from a specific PID before moving on to the next, regardless of which message is at the top of the mailbox.
By capturing the PIDs of the spawned processes and using the Pin operator (^pid) in the receive block. This forces the parent to wait for the message from a specific PID before moving on to the next, regardless of which message is at the top of the mailbox.
Q
Write the logical structure for a persistent 'Token' receiver using TCO.
Solution:
The receiver should be a function (e.g., 'loop') that contains a 'receive' block. After processing the token and sending it back, it must call 'loop()' as the final statement. This allows the process to handle multiple tokens over its lifetime without crashing the stack.
The receiver should be a function (e.g., 'loop') that contains a 'receive' block. After processing the token and sending it back, it must call 'loop()' as the final statement. This allows the process to handle multiple tokens over its lifetime without crashing the stack.